﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Web.Script.Serialization;
using System.Windows.Forms;

namespace AutoUpdate
{
    public partial class FrmMain : Form
    {
        private string ServiceType = ConfigurationManager.AppSettings["ServiceType"];
        private string userName = ConfigurationManager.AppSettings["UserName"];
        private string password = ConfigurationManager.AppSettings["Password"];
        private string remoteConfigPath = ConfigurationManager.AppSettings["RemoteConfigPath"];

             
        string webRootPath;
        JavaScriptSerializer convert = new JavaScriptSerializer();
        long currentIndex, currentCount;
        string currentFileName = "";
        long totalIndex, totalCount;

        private HttpClient client = new HttpClient();

        /// <summary>
        /// 判断是更新还是在线安装，如果本地存在主程序，则更新，否则在线安装
        /// </summary>
        public bool IsUpdate = true;

        /// <summary>
        /// 在Program中初始化
        /// </summary>
        public UpdateInfo updateInfo;

        public FrmMain()
        {
            InitializeComponent();            
        }

        /// <summary>
        /// 窗体首次显示执行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FrmMain_Shown(object sender, EventArgs e)
        {       
            new Thread(() =>
            {               
                if (updateInfo == null)
                {
                    this.Invoke(new Action(() =>
                    {
                        MessageBoxEx.Show("获取更新配置失败!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        this.Close();
                    }));
                    return;
                }
                else
                {
                    this.Invoke(new Action(() =>
                    {
                        try
                        {
                            if (!string.IsNullOrEmpty(updateInfo.UpdateInfoUrl))
                                this.webBrowser.Url = new Uri(updateInfo.UpdateInfoUrl);

                            if (!File.Exists(updateInfo.MainProgramName))
                            {
                                IsUpdate = false;
                            }
                        }
                        catch
                        {
                        }
                    }));
                }
                this.currentFileName = "文件校验中...";

                this.webRootPath = updateInfo.RootUrl;
                this.totalIndex = 0;
                //删除本地需要更新的本地文件
                if (IsUpdate)   //升级
                {
                    updateInfo.OnlineUpdateFileList.RemoveAll(file =>
                    {
                        var filepath = Application.StartupPath + file.Directory + "\\" + file.Name;
                        Console.WriteLine(filepath);
                        return file.MD5 == GetMD5HashFromFile(filepath);
                    });
                    this.totalCount = updateInfo.OnlineUpdateFileList.Sum(s => s.Size);

                    if (this.DownloadFileList(updateInfo.OnlineUpdateFileList))
                    {
                        Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                        config.AppSettings.Settings["VerCode"].Value = this.updateInfo.VerCode.ToString();
                        config.Save(ConfigurationSaveMode.Modified);
                    }
                }
                else      //在线安装
                {
                    this.totalCount = updateInfo.OnlineInstallFileList.Sum(s => s.Size);
                    if (this.DownloadFileList(updateInfo.OnlineInstallFileList))
                    {
                        Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                        config.AppSettings.Settings["VerCode"].Value = this.updateInfo.VerCode.ToString();
                        config.Save(ConfigurationSaveMode.Modified);
                    }

                    //if (string.IsNullOrWhiteSpace(updateInfo.InstallProgramName))
                    //{
                    //    this.totalCount = updateInfo.OnlineInstallFileList.Sum(s => s.Size);
                    //    if (this.DownloadFileList(updateInfo.OnlineInstallFileList))
                    //    {
                    //        Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                    //        config.AppSettings.Settings["VerCode"].Value = this.updateInfo.VerCode.ToString();
                    //        config.Save(ConfigurationSaveMode.Modified);
                    //    }
                    //}
                    //else 
                    //{
                    //    this.totalCount = updateInfo.OnlineInstallFileList.Sum(s => s.Size);
                    //    if (this.DownloadFileList(updateInfo.OnlineInstallFileList))
                    //    {
                    //        Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                    //        config.AppSettings.Settings["VerCode"].Value = this.updateInfo.VerCode.ToString();
                    //        config.Save(ConfigurationSaveMode.Modified);
                    //    }
                    //}

                }

                this.Invoke(new Action(() =>
                {
                    this.Close();
                }));
            })
            { IsBackground = true }.Start();
        }

        /// <summary>
        /// 从服务器下载文件
        /// </summary>
        /// <param name="remotePath"></param>
        /// <param name="remoteFileName"></param>
        /// <param name="localPath"></param>
        /// <param name="localFileName"></param>   
        /// <returns></returns>
        public bool Download(string remotePath, string remoteFileName, string localPath, string localFileName)
        {
            string url = string.Format("{0}/{1}", remotePath, remoteFileName);
            Console.WriteLine(url);

            var localDir = AppDomain.CurrentDomain.BaseDirectory + 
                           localPath.Replace("\\installFiles", "")
                               .Replace("\\updateFiles", "");
            FileStream outputStream = null;
            if (!Directory.Exists(localDir))
                Directory.CreateDirectory(localDir);

            outputStream = new FileStream(Path.Combine(localDir, localFileName), FileMode.Create);

            if (this.ServiceType == "1")
            {                
                try
                {
                    FtpWebRequest reqFTP = (FtpWebRequest)WebRequest.Create(new Uri(url));
                    reqFTP.Method = WebRequestMethods.Ftp.DownloadFile;
                    reqFTP.UseBinary = true;
                    reqFTP.KeepAlive = true;
                    if (!string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
                    {
                        reqFTP.Credentials = new NetworkCredential(userName, password);
                    } 
                    
                    FtpWebResponse response = (FtpWebResponse)reqFTP.GetResponse();
                    Stream ftpStream = response.GetResponseStream();
                    int bufferSize = 2048, readCount = 0;
                    byte[] buffer = new byte[bufferSize];
                    while (true)
                    {
                        readCount = ftpStream.Read(buffer, 0, bufferSize);
                        if (readCount == 0)
                            break;
                        outputStream.Write(buffer, 0, readCount);
                        currentIndex += readCount;
                        totalIndex += readCount;
                    }
                    ftpStream.Close();
                    response.Close();
                    return true;
                }
                catch
                {
                    return false;
                }
                finally
                {
                    if (outputStream != null)
                        outputStream.Close();
                }
            }
            else if (this.ServiceType == "2")
            {
                //http更新
                if (!string.IsNullOrEmpty(this.userName) && !string.IsNullOrEmpty(this.password))
                {
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{this.userName}:{this.password}")));
                }

                Uri address = new Uri(url);

                try 
                {
                    var name = string.Format("{0}\\{1}", localDir, localFileName);
                    byte[] buffer = client.GetByteArrayAsync(url).Result;
                    outputStream.Write(buffer, 0, buffer.Length);
                    currentIndex += buffer.Length;
                    totalIndex += buffer.Length;
                    return true;
                }
                catch(Exception ex)
                {
                    return false;
                }
                finally
                {
                    if (outputStream != null)
                        outputStream.Close();
                }
            }
            return false;
        }

        public bool DownloadFileList(List<FileItem> fileList)
        {
            var result = true;
            foreach (var fileItem in fileList)
            {
                currentIndex = 0;
                currentCount = fileItem.Size;
                currentFileName = fileItem.Name;
                var fileResult = false;
                if (fileItem.Name == "AutoUpdate.exe")
                    fileResult = this.Download(webRootPath + fileItem.Directory.Replace('\\','/'), fileItem.Name, fileItem.Directory, "AutoUpdate.exe.tmp");
                else
                    fileResult = this.Download(webRootPath + fileItem.Directory.Replace('\\', '/'), fileItem.Name, fileItem.Directory, fileItem.Name);
                result = result && fileResult;
            }
            return result;
        }

        private void timerUpdate_Tick(object sender, EventArgs e)
        {
            this.labFileName.Text = this.currentFileName;
            this.labFileSize.Text = FormatFileSize(currentIndex) + "/" + FormatFileSize(currentCount);
            this.labFileSize.Left = this.Width - this.labFileSize.Width - 30;
            if (this.currentCount > 0)
            {
                this.pbCurrent.Value = this.currentIndex / (float)this.currentCount;
                this.pbCurrent.Invalidate();
            }
            if (this.totalCount > 0)
            {
                this.pbTotal.Value = this.totalIndex / (float)this.totalCount;
                this.pbTotal.Invalidate();
            }
        }

        public static string GetMD5HashFromFile(string fileName)
        {
            try
            {
                FileStream file;
                if (File.Exists(fileName))
                    file = File.OpenRead(fileName);
                else
                    return null;
                System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                byte[] retVal = md5.ComputeHash(file);
                file.Close();
                StringBuilder str = new StringBuilder();
                for (int i = 0; i < retVal.Length; i++)
                    str.Append(retVal[i].ToString("X2"));
                return str.ToString();
            }
            catch
            {
                return null;
            }
        }

        internal static string FormatFileSize(double fileSize)
        {
            if (fileSize < 0)
                return "ErrorSize";
            else if (fileSize >= 1024 * 1024 * 1024)
                return string.Format("{0:########0.00} GB", fileSize / (1024 * 1024 * 1024));
            else if (fileSize >= 1024 * 1024)
                return string.Format("{0:####0.00} MB", fileSize / (1024 * 1024));
            else if (fileSize >= 1024)
                return string.Format("{0:####0.00} KB", fileSize / 1024);
            else
                return string.Format("{0} Bytes", fileSize);
        }
    }
}
